package me.kuye.jkuye.core;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import me.kuye.jkuye.aop.Interceptor;
import me.kuye.jkuye.config.Interceptors;
import me.kuye.jkuye.config.Routes;

public final class ActionMapping {
	private static final String SLASH = "/";
	private Routes routes;
	private Interceptors interceptoers;
	private final Map<String, Action> actionMap = new HashMap<String, Action>();

	public ActionMapping(Routes routes, Interceptors interceptors) {
		this.routes = routes;
		this.interceptoers = interceptors;
	}

	public void buildActionMapping() {
		actionMap.clear();
		Set<String> excludedMethodName = buildExcludedMethodName();
		ActionInterceptorBuilder actionInterceptorBuilder = new ActionInterceptorBuilder();
		Interceptor[] globalInters = interceptoers.getGlobalActionInterceptor();
		actionInterceptorBuilder.addToInterceptorsMap(globalInters);
		for (Entry<String, Class<? extends Controller>> entry : routes.getEntrySet()) {
			Class<? extends Controller> controllerClass = entry.getValue();
			Interceptor[] controllerInterceptors = actionInterceptorBuilder.buildControllerInterceptor(controllerClass);

			boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
			Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());

			for (Method method : methods) {
				String methodName = method.getName();
				if (excludedMethodName.contains(methodName) || method.getParameterTypes().length > 0) {
					continue;
				}
				if (sonOfController && !Modifier.isPublic(method.getModifiers())) {
					continue;
				}
				Interceptor[] methodInterceptors = actionInterceptorBuilder.buildMethodInterceptors(method);
				Interceptor[] actionInterceptors = actionInterceptorBuilder.buildActionInterceptors(globalInters,
						controllerInterceptors, methodInterceptors, method);
				String controllerKey = entry.getKey();
				ActionKey ak = method.getAnnotation(ActionKey.class);
				String actionKey;
				if (ak != null) {
					actionKey = ak.value().trim();
					if ("".equals(actionKey)) {
						throw new IllegalArgumentException("the class" + controllerClass.getName() + "."
								+ method.getName() + " actionKey can not be null");
					}

					if (!actionKey.startsWith(SLASH))
						actionKey = SLASH + actionKey;
				} else if (methodName.equals("index")) {
					actionKey = controllerKey;
				} else {
					actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
				}
				Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName,
						actionInterceptors, routes.getViewPath(controllerKey));
				if (actionMap.put(actionKey, action) != null)
					throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
			}
		}
	}

	private String buildMsg(String actionKey, Class<? extends Controller> controllerClass, Method method) {
		StringBuilder sb = new StringBuilder("The action \"").append(controllerClass.getName()).append(".")
				.append(method.getName()).append("()\" can not be mapped, ").append("actionKey \"").append(actionKey)
				.append("\" is already in use.");

		String msg = sb.toString();
		System.err.println("\nException: " + msg);
		return msg;
	}

	private Set<String> buildExcludedMethodName() {
		Set<String> excludedMethodName = new HashSet<String>();
		Method[] methods = Controller.class.getMethods();
		for (int i = 0; i < methods.length; i++) {
			if (methods[i].getTypeParameters().length > 0) {
				excludedMethodName.add(methods[i].getName());
			}
		}
		return excludedMethodName;
	}

	public Action getAction(String target, String[] urlPara) {
		Action action = actionMap.get(target);
		if (action != null) {
			return action;
		}

		int i = target.lastIndexOf(SLASH);
		if (i != -1) {
			action = actionMap.get(target.substring(0, i));
			urlPara[0] = target.substring(i + 1);
		}
		return action;
	}
}
